﻿#include "Util.h"

#include <sstream>
#include <fstream>
#include <algorithm>
#include <filesystem>

namespace Lisp
{

  namespace StringUtils
  {

    std::string Trim(const std::string &str)
    {
      std::string &trimmed = const_cast<std::string &>(str);
      trimmed.erase(trimmed.begin(), std::find_if(trimmed.begin(), trimmed.end(), [](unsigned char ch)
                                                  { return !std::isspace(ch); }));

      return trimmed;
    }

    std::string Escape(const std::string &str)
    {
      std::string out;
      out.reserve(str.size() * 2 + 2); // each char may get escaped + two "'s
      out += '"';
      for (auto it = str.begin(), end = str.end(); it != end; ++it)
      {
        char c = *it;
        switch (c)
        {
        case '\\':
          out += "\\\\";
          break;
        case '\n':
          out += "\\n";
          break;
        case '"':
          out += "\\\"";
          break;
        default:
          out += c;
          break;
        };
      }
      out += '"';
      out.shrink_to_fit();
      return out;
    }

    static char Unescape(char c)
    {
      switch (c)
      {
      case '\\':
        return '\\';
      case 'n':
        return '\n';
      case '"':
        return '"';
      default:
        return c;
      }
    }

    std::string Unescape(const std::string &str)
    {
      std::string out;
      out.reserve(str.size()); // unescaped string will always be shorter

      // in will have double-quotes at either end, so move the iterators in
      for (auto it = str.begin() + 1, end = str.end() - 1; it != end; ++it)
      {
        char c = *it;
        if (c == '\\')
        {
          ++it;
          if (it != end)
          {
            out += Unescape(*it);
          }
        }
        else
        {
          out += c;
        }
      }
      out.shrink_to_fit();
      return out;
    }

  } // namespace StringUtils

  namespace FileUtils
  {
    std::string ReadFile(const std::string &path)
    {
      std::ios_base::openmode openmode =
          std::ios::ate | std::ios::in | std::ios::binary;
      std::ifstream file(path.c_str(), openmode);
      LISP_CHECK(!file.fail(), "Cannot open %s", path.c_str());

      std::string data;
      data.reserve(file.tellg());
      file.seekg(0, std::ios::beg);
      data.append(std::istreambuf_iterator<char>(file.rdbuf()),
                  std::istreambuf_iterator<char>());

      // 检查并移除UTF-8 BOM (0xEF 0xBB 0xBF)
      if (data.size() >= 3 &&
          static_cast<unsigned char>(data[0]) == 0xEF &&
          static_cast<unsigned char>(data[1]) == 0xBB &&
          static_cast<unsigned char>(data[2]) == 0xBF)
      {
        data = data.substr(3);
      }
      return data;
    }

    std::vector<std::string> ListFiles(const std::string &path, const std::string &ext)
    {
      std::filesystem::path dir(path);
      std::vector<std::string> filesNames;

      for (auto &p : std::filesystem::directory_iterator(dir))
      {
        if (p.is_regular_file() && p.path().extension() == ext)
        {
          filesNames.push_back(p.path().string());
        }
      }
      return filesNames;
    }
  } // namespace FileUtils

  namespace TimeUtils
  {
    int64_t Now()
    {
      return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
    }
  } // namespace TimeUtils

} // namespace Lisp
